# Copyright (c) 2018 Presto Labs Pte. Ltd.
# Author: inkyu

import logging

import tabulate

from coin.proto.coin_feed_checker_pb2 import FeedCheckerProductConfig, FeedCheckerConfig

from coin.exchange.util.feed_checker.types import FeedCheckerState
from coin.exchange.util.feed_checker.per_product import (PerProductFeedChecker,
                                                         PerProductFeedCheckerStatus)
from coin.exchange.util.feed_checker.bootup import BootupChecker


class FeedChecker(object):
  def __init__(self, feed_checker_config, *, logger=None):
    self._config = feed_checker_config

    self._products = set()
    self._per_product = {}
    self._bootup_checker = None
    self._logger = logger or logging.getLogger(__name__)
    self._valid = True

  @property
  def _is_running(self):
    return bool(self._per_product)

  def add_product(self, product):
    # TODO(inkyu): Move this to the constructor
    assert not self._is_running, 'FeedChecker is running'
    self._products.add(product)

  def start(self, *, timestamp):
    assert not self._is_running, 'FeedChecker is running'
    self._valid = True
    self._per_product = {
        product: PerProductFeedChecker(product=product,
                                       config=self._config.per_product[str(product)],
                                       timestamp=timestamp) for product in self._products
    }
    self._bootup_checker = BootupChecker(bootup_time=self._config.common.bootup_time,
                                         timestamp=timestamp)

  def stop(self, *, timestamp):
    self._valid = False
    self._per_product = {}
    self._bootup_checker = None

  def restart(self, *, timestamp):
    self.stop(timestamp=timestamp)
    self.start(timestamp=timestamp)

  def on_book(self, *, product, book, timestamp):
    if not self._valid:
      return
    per_product = self._per_product.get(product, None)
    if per_product:
      per_product.on_book(book=book, timestamp=timestamp)
      if not per_product.valid:
        self._valid = False
      self._bootup_checker.update_book_count(book_count=per_product.book_count, timestamp=timestamp)

  def on_trade(self, *, product, trade_event, timestamp):
    if not self._valid:
      return
    per_product = self._per_product.get(product, None)
    if per_product:
      per_product.on_trade(trade_event=trade_event, timestamp=timestamp)
      if not per_product.valid:
        self._valid = False

  def check_state(self, *, timestamp):
    if not self._valid:
      return FeedCheckerState.INVALID
    for per_product in self._per_product.values():
      per_product.update_timestamp(timestamp=timestamp)
      if not per_product.valid:
        self._valid = False
        return FeedCheckerState.INVALID
    if self._bootup_checker is not None:
      self._bootup_checker.check_state(timestamp=timestamp)
      return self._bootup_checker.check_state(timestamp=timestamp)
    return FeedCheckerState.READY

  # This is used when to replay feed, for instance in feed stats application.
  def check_per_product_state(self, *, product, timestamp):
    if not self._valid:
      return FeedCheckerState.INVALID
    assert product in self._products
    per_product = self._per_product[product]
    per_product.update_timestamp(timestamp=timestamp)
    if not per_product.valid:
      self._valid = False
      return FeedCheckerState.INVALID
    return FeedCheckerState.READY

  def print_status(self, timestamp):
    table = []
    headers = PerProductFeedCheckerStatus._fields
    table = [
        per_product_book_checker.get_status(timestamp=timestamp)
        for per_product_book_checker in self._per_product.values()
    ]
    output = tabulate.tabulate(table, headers=headers)
    self._logger.info('\n' + output)


def get_default_feed_checker_config(products):
  if not products:  # products list is empty
    return None

  per_product = {}
  bootup_time = None
  for product in products:
    if product.exchange == 'Shared':
      per_product[str(product)] = FeedCheckerProductConfig(max_book_gap=10e9,
                                                           max_book_change_gap=30e9,
                                                           max_trade_gap=20e9,
                                                           max_trade_change_gap=60e9)
    if product.exchange == 'Bitflyer':
      if product.symbol == 'BTC-JPY.PERPETUAL':
        per_product[str(product)] = FeedCheckerProductConfig(max_book_gap=5e9,
                                                             max_book_change_gap=10e9,
                                                             max_trade_gap=5e9,
                                                             max_trade_change_gap=20e9)
      elif product.symbol == 'BTC-JPY.IMMEDIATE':
        per_product[str(product)] = FeedCheckerProductConfig(max_book_gap=5e9,
                                                             max_book_change_gap=10e9,
                                                             max_trade_gap=60e9,
                                                             max_trade_change_gap=60e9)
      else:
        per_product[str(product)] = FeedCheckerProductConfig(max_book_gap=5e9,
                                                             max_book_change_gap=10e9,
                                                             max_trade_gap=60e9,
                                                             max_trade_change_gap=60e9)
    elif product.exchange == 'Bitmex':
      per_product[str(product)] = FeedCheckerProductConfig(max_book_gap=5e9,
                                                           max_book_change_gap=60e9,
                                                           max_trade_gap=16e9,
                                                           max_trade_change_gap=60e9)
    elif product.exchange == 'Okex' and product.product_type == 'Futures':
      if product.base.symbol in ('BTC', 'ETH', 'EOS') and \
          product.quote.currency == 'USD':
        if product.contract_type == 'this_week':
          per_product[str(product)] = FeedCheckerProductConfig(max_book_gap=5e9,
                                                               max_book_change_gap=20e9,
                                                               max_trade_gap=120e9,
                                                               max_trade_change_gap=120e9)
        elif product.contract_type in ('next_week', 'next_quarter'):
          per_product[str(product)] = FeedCheckerProductConfig(max_book_gap=5e9,
                                                               max_book_change_gap=20e9,
                                                               max_trade_gap=360e9,
                                                               max_trade_change_gap=360e9)
        elif product.contract_type == 'quarter':
          per_product[str(product)] = FeedCheckerProductConfig(max_book_gap=5e9,
                                                               max_book_change_gap=20e9,
                                                               max_trade_gap=120e9,
                                                               max_trade_change_gap=120e9)
        elif product.is_perpetual:
          per_product[str(product)] = FeedCheckerProductConfig(max_book_gap=10e9,
                                                               max_book_change_gap=20e9,
                                                               max_trade_gap=360e9,
                                                               max_trade_change_gap=360e9)
      elif product.base.symbol in ('LTC', 'XRP', 'ETC', 'BCHABC', 'BCHSV', 'TRX') and \
          product.quote.currency == 'USD':
        if product.contract_type == 'this_week':
          per_product[str(product)] = FeedCheckerProductConfig(max_book_gap=5e9,
                                                               max_book_change_gap=20e9,
                                                               max_trade_gap=240e9,
                                                               max_trade_change_gap=240e9)
        elif product.contract_type in ('next_week', 'next_quarter'):
          per_product[str(product)] = FeedCheckerProductConfig(max_book_gap=5e9,
                                                               max_book_change_gap=20e9,
                                                               max_trade_gap=360e9,
                                                               max_trade_change_gap=360e9)
        elif product.contract_type == 'quarter':
          per_product[str(product)] = FeedCheckerProductConfig(max_book_gap=5e9,
                                                               max_book_change_gap=20e9,
                                                               max_trade_gap=240e9,
                                                               max_trade_change_gap=240e9)
        elif product.is_perpetual:
          per_product[str(product)] = FeedCheckerProductConfig(max_book_gap=10e9,
                                                               max_book_change_gap=20e9,
                                                               max_trade_gap=360e9,
                                                               max_trade_change_gap=360e9)
      else:  # USDT futures
        if product.contract_type == 'this_week':
          per_product[str(product)] = FeedCheckerProductConfig(max_book_gap=30e9,
                                                               max_book_change_gap=60e9,
                                                               max_trade_gap=240e9,
                                                               max_trade_change_gap=240e9)
        elif product.contract_type in ('next_week', 'next_quarter'):
          per_product[str(product)] = FeedCheckerProductConfig(max_book_gap=30e9,
                                                               max_book_change_gap=60e9,
                                                               max_trade_gap=360e9,
                                                               max_trade_change_gap=360e9)
        elif product.contract_type == 'quarter':
          per_product[str(product)] = FeedCheckerProductConfig(max_book_gap=30e9,
                                                               max_book_change_gap=60e9,
                                                               max_trade_gap=240e9,
                                                               max_trade_change_gap=240e9)
    elif product.exchange == 'Binance':
      per_product[str(product)] = FeedCheckerProductConfig(max_book_gap=10e9,
                                                           max_book_change_gap=20e9,
                                                           max_trade_gap=360e9,
                                                           max_trade_change_gap=360e9)
    elif product.exchange == 'Fcoin':
      per_product[str(product)] = FeedCheckerProductConfig(max_book_gap=5e9,
                                                           max_book_change_gap=20e9,
                                                           max_trade_gap=360e9,
                                                           max_trade_change_gap=360e9)
    elif product.exchange == 'Hitbtc':
      per_product[str(product)] = FeedCheckerProductConfig(max_book_gap=10e9,
                                                           max_book_change_gap=20e9,
                                                           max_trade_gap=360e9,
                                                           max_trade_change_gap=360e9)
    elif product.exchange == 'Bequant':
      per_product[str(product)] = FeedCheckerProductConfig(max_book_gap=10e9,
                                                           max_book_change_gap=20e9,
                                                           max_trade_gap=360e9,
                                                           max_trade_change_gap=360e9)
    elif product.exchange == 'Quoinex':
      per_product[str(product)] = FeedCheckerProductConfig(max_book_gap=10e9,
                                                           max_book_change_gap=10e9,
                                                           max_trade_gap=20e9,
                                                           max_trade_change_gap=20e9)
    elif product.exchange == 'Gdax':
      per_product[str(product)] = FeedCheckerProductConfig(max_book_gap=10e9,
                                                           max_book_change_gap=20e9,
                                                           max_trade_gap=360e9,
                                                           max_trade_change_gap=360e9)
    elif product.exchange == 'Bitstamp':
      per_product[str(product)] = FeedCheckerProductConfig(max_book_gap=10e9,
                                                           max_book_change_gap=20e9,
                                                           max_trade_gap=360e9,
                                                           max_trade_change_gap=360e9)
    elif product.exchange == 'Korbit':
      per_product[str(product)] = FeedCheckerProductConfig(max_book_gap=30e9,
                                                           max_book_change_gap=60e9,
                                                           max_trade_gap=600e9,
                                                           max_trade_change_gap=600e9)
    elif product.exchange == 'Coinone':
      per_product[str(product)] = FeedCheckerProductConfig(max_book_gap=30e9,
                                                           max_book_change_gap=60e9,
                                                           max_trade_gap=600e9,
                                                           max_trade_change_gap=600e9)
    elif product.exchange == 'Gopax':
      per_product[str(product)] = FeedCheckerProductConfig(max_book_gap=60e9,
                                                           max_book_change_gap=60e9,
                                                           max_trade_gap=600e9,
                                                           max_trade_change_gap=600e9)
    elif product.exchange == 'Deribit':
      per_product[str(product)] = FeedCheckerProductConfig(max_book_gap=5e9,
                                                           max_book_change_gap=60e9,
                                                           max_trade_gap=240e9,
                                                           max_trade_change_gap=240e9)
    elif product.exchange == 'Bybit':
      per_product[str(product)] = FeedCheckerProductConfig(max_book_gap=30e9,
                                                           max_book_change_gap=60e9,
                                                           max_trade_gap=240e9,
                                                           max_trade_change_gap=240e9)
    elif product.exchange == 'Uniswap':
      per_product[str(product)] = FeedCheckerProductConfig(max_book_gap=15e9,
                                                           max_book_change_gap=3600e9,
                                                           max_trade_gap=3600e9,
                                                           max_trade_change_gap=3600e9)
    elif product.exchange == 'Bithumb':
      if product.base.symbol in ('BTC',):
        per_product[str(product)] = FeedCheckerProductConfig(max_book_gap=10e9,
                                                             max_book_change_gap=80e9,
                                                             max_trade_gap=80e9,
                                                             max_trade_change_gap=80e9)
      elif product.base.symbol in ('ETH', 'EOS'):
        per_product[str(product)] = FeedCheckerProductConfig(max_book_gap=30e9,
                                                             max_book_change_gap=80e9,
                                                             max_trade_gap=150e9,
                                                             max_trade_change_gap=150e9)
      else:
        per_product[str(product)] = FeedCheckerProductConfig(max_book_gap=30e9,
                                                             max_book_change_gap=80e9,
                                                             max_trade_gap=150e9,
                                                             max_trade_change_gap=150e9)
    elif product.exchange in ('Bitfinex', 'Okex', 'Upbit', 'Kraken', 'Huobi'):
      if product.exchange == 'Upbit':
        bootup_time = 3.0e9
      if product.base.symbol in ('BTC',):
        per_product[str(product)] = FeedCheckerProductConfig(max_book_gap=10e9,
                                                             max_book_change_gap=15e9,
                                                             max_trade_gap=360e9,
                                                             max_trade_change_gap=360e9)
      elif product.base.symbol in ('ETH', 'EOS'):
        per_product[str(product)] = FeedCheckerProductConfig(max_book_gap=20e9,
                                                             max_book_change_gap=30e9,
                                                             max_trade_gap=720e9,
                                                             max_trade_change_gap=720e9)
      else:
        per_product[str(product)] = FeedCheckerProductConfig(max_book_gap=120e9,
                                                             max_book_change_gap=180e9,
                                                             max_trade_gap=900e9,
                                                             max_trade_change_gap=900e9)

  feed_checker_config = FeedCheckerConfig(common=FeedCheckerProductConfig(max_book_gap=30e9,
                                                                          max_book_change_gap=60e9,
                                                                          max_trade_gap=30e9,
                                                                          max_trade_change_gap=60e9,
                                                                          bootup_time=bootup_time),
                                          per_product=per_product)
  return feed_checker_config
